package com.threeti.mecool.core.util;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.repository.CrudRepository;
import org.springframework.util.CollectionUtils;

import com.threeti.jiayusms.framework.util.Lists;
import com.threeti.mecool.shared.util.AbstractFlatRawTreeNode;
import com.threeti.mecool.shared.util.AbstractTreeNode;

public abstract class TreeHelper {
  private static final Logger logger = LoggerFactory.getLogger(TreeHelper.class);
  
  public static <T extends AbstractTreeNode<TK>, TK extends Serializable>List<T> templateBuildTree(
      List<? extends AbstractFlatRawTreeNode<TK>> flatRawTree, 
      Class resultRichTreeNodeClazz, 
      CrudRepository rawTreeNodeRepo) {
    return templateBuildTree(flatRawTree, resultRichTreeNodeClazz, rawTreeNodeRepo, null);
  }

  public static <T extends AbstractTreeNode<TK>, TK extends Serializable>List<T> templateBuildTree(
      List<? extends AbstractFlatRawTreeNode<TK>> flatRawTree, 
      Class resultRichTreeNodeClazz, 
      CrudRepository rawTreeNodeRepo,
      TK relativeSingleRootNodeId) {
    //1.build tree's fast image
    Object[] treeFastImages = buildFastImagesOfFlatTree(flatRawTree, relativeSingleRootNodeId);

    Map<TK, LinkedHashSet<TK>> treeFastImage = (Map<TK, LinkedHashSet<TK>>)treeFastImages[0];
    List<TK> topLevelNodeIds = (List<TK>)treeFastImages[1];
    
    //2.through tree's fast image,build part of top level channelCategories(this kind of top level nodes have childs)
    List<T> resultTopLevelNodes = buildTopLevelTreeNodesWhoHaveChilds(treeFastImage, topLevelNodeIds, resultRichTreeNodeClazz, rawTreeNodeRepo);
    
    System.out.println("treeFastImage: " + treeFastImage);
    
    //3.build rest top level channelCategories(this kind of top level nodes have no childs)
    buildRestTopLevelTreeNodesWhoHaveNoChilds(topLevelNodeIds, resultTopLevelNodes, resultRichTreeNodeClazz, rawTreeNodeRepo);
    

    System.out.println("topLevelNodeIds(without childs): " + topLevelNodeIds);
    return resultTopLevelNodes;
  }

  private static <TK extends Serializable>Object[] buildFastImagesOfFlatTree(List<? extends AbstractFlatRawTreeNode<TK>> flatRawTree) {    
    return buildFastImagesOfFlatTree(flatRawTree, null);
  }

  private static <TK extends Serializable>Object[] buildFastImagesOfFlatTree(List<? extends AbstractFlatRawTreeNode<TK>> flatRawTree, TK relativeSingleRootNodeId) {
    Map<TK, LinkedHashSet<TK>> treeFastImage = new HashMap<TK, LinkedHashSet<TK>>();
    List<TK> topLevelNodeIds = new ArrayList<TK>();
    boolean isRootNodeSpecified = (relativeSingleRootNodeId != null);
    
    if (isRootNodeSpecified) {
      //only store specified single root
      topLevelNodeIds.add(relativeSingleRootNodeId);
      
      for (AbstractFlatRawTreeNode<TK> node : flatRawTree) {
        TK currentNodeId = node.getId();
        TK currentParentId = node.getParentId();
        
        innerBuildFastImagesOfFlatTree(treeFastImage, currentNodeId, currentParentId);
      }
    } else {
      for (AbstractFlatRawTreeNode<TK> node : flatRawTree) {
        TK currentNodeId = node.getId();
        TK currentParentId = node.getParentId();
        if (currentParentId == null) {
          //store all the top level nodes
          topLevelNodeIds.add(currentNodeId);
        } else {
          innerBuildFastImagesOfFlatTree(treeFastImage, currentNodeId, currentParentId);
        }
      }
    }
    
    return new Object[]{treeFastImage, topLevelNodeIds};
  }

  private static <TK extends Serializable>void innerBuildFastImagesOfFlatTree(Map<TK, LinkedHashSet<TK>> treeFastImage, TK currentNodeId, TK currentParentId) {
    LinkedHashSet<TK> currentChildIds = null;
    if ((currentChildIds = treeFastImage.get(currentParentId)) != null) {
      currentChildIds.add(currentNodeId);//join as another child next to current last child of current parent node
    } else {
      currentChildIds = new LinkedHashSet<TK>();
      currentChildIds.add(currentNodeId);//join as the first child of current parent node
      treeFastImage.put(currentParentId, currentChildIds);
    }
  }

  private static <T extends AbstractTreeNode<TK>,TK extends Serializable> List<T> buildTopLevelTreeNodesWhoHaveChilds(
      Map<TK, LinkedHashSet<TK>> treeFastImage, 
      List<TK> topLevelNodeIds,
      Class resultRichTreeNodeClazz, CrudRepository rawTreeNodeRepo) {
    List<T> resultTopLevelNodes = Lists.newArrayList();
    
    Set<Entry<TK, LinkedHashSet<TK>>> treePreImageSets = treeFastImage.entrySet();
    Map<TK, T> treeRegistry = new HashMap<TK, T>();
    for (Entry<TK, LinkedHashSet<TK>> singleDeepParentWithDirectChilds : treePreImageSets) {
      //fetch parent
      TK parentId = singleDeepParentWithDirectChilds.getKey();      
      //get parent from tree registry(or register it self) to see whether it is existed or already be a child of its parent,
      //for the purpose maintaining always the same parent reference
      T parentNode =  buildConnectionWithTheTree(treeRegistry, parentId, resultRichTreeNodeClazz, rawTreeNodeRepo);
      
      //fetch direct childs
      LinkedHashSet<TK> directChildIds = singleDeepParentWithDirectChilds.getValue();
      for (TK directChildId : directChildIds) {        
        //get child from tree registry(or register it self) to see whether it is existed or already be a parent of its child,
        //for the purpose maintaining always the same child reference
        T childNode = buildConnectionWithTheTree(treeRegistry, directChildId, resultRichTreeNodeClazz, rawTreeNodeRepo);
        parentNode.addChild(childNode);
      }
      //as result,build top level nodes(this kind of top level nodes have childs)
      if (topLevelNodeIds.contains(parentId)) {
        resultTopLevelNodes.add(parentNode);
        topLevelNodeIds.remove(parentId);
      }
    }
    
    return resultTopLevelNodes;
  }

  private static <T, TK extends Serializable>void buildRestTopLevelTreeNodesWhoHaveNoChilds(List<TK> topLevelNodeIds, List<T> resultTopLevelNodes, Class resultRichTreeNodeClazz, CrudRepository rawTreeNodeRepo) {
    try {
      for (TK topLevelNodeId : topLevelNodeIds) {
        AbstractFlatRawTreeNode<TK> topLevelNodeWithNoChild = (AbstractFlatRawTreeNode<TK>)rawTreeNodeRepo.findOne(topLevelNodeId);
        T node = (T)resultRichTreeNodeClazz.getConstructors()[0].newInstance(topLevelNodeWithNoChild.getId(), topLevelNodeWithNoChild.getName());//TODO jay:cache reflection?
        //ChannelCategory childChannelCategory = new ChannelCategory(topLevelNodeWithNoChild.getCode(), topLevelNodeWithNoChild.getName());
        resultTopLevelNodes.add(node);
      }
    } catch (Exception e) {
      logger.debug("创建树失败", e);
      throw new RuntimeException("创建树失败", e);
    }
  }

  private static <T,K extends Serializable> T buildConnectionWithTheTree(Map<K, T> treeRegistry, K nodeId, Class resultRichTreeNodeClazz, CrudRepository repo) {
    try {
      T node;
      if ((node = treeRegistry.get(nodeId)) == null) {
        AbstractFlatRawTreeNode<K> theExistParentOrChild = (AbstractFlatRawTreeNode<K>)repo.findOne(nodeId);
        node = (T)resultRichTreeNodeClazz.getConstructors()[0].newInstance(theExistParentOrChild.getId(), theExistParentOrChild.getName());//TODO jay:cache reflection?
        treeRegistry.put(nodeId, node);
      }
      return node;
    } catch (Exception e) {
      logger.debug("创建树失败", e);
      throw new RuntimeException("创建树失败", e);
    }
  }
  
  public static <TK extends Serializable>void buildTree(AbstractTreeNode<TK> entryNode) {
    
    if (CollectionUtils.isEmpty(entryNode.getChilds())) {
      System.out.println("leaf node:" + entryNode +",with parent:"+(entryNode.getParent()!=null?entryNode.getParent():"null"));
      return;//touch leaf node
    } else {
      for (AbstractTreeNode childNode : entryNode.getChilds()) {
        System.out.println("normal node:" + childNode +",with parent:"+(childNode.getParent()!=null?childNode.getParent():"null"));
        buildTree(childNode);
      }
    }    
  }
}
